#!/usr/bin/env python

__author__ = "Abhinav Sarkar"
__version__ = "0.1"
__license__ = "GNU Lesser General Public License"

class User (object):
	"""A class representing an user.

	The User class exposes the following instance properties:

		user.id
		user.cluster
		user.username
		user.url
		user.realname
		user.registered
		user.age
		user.gender
		user.country
		user.playcount
		user.avatar
		user.icon
		user.fullProfile
		user.topArtists
		user.topArtist
		user.topAlbums
		user.topAlbum
		user.topTracks
		user.topTrack
		user.topTags
		user.topTag
		user.friends
		user.neighbours
		user.recentTracks
		user.mostRecentTrack
		user.recentLovedTracks
		user.recentBannedTracks
		user.recentWeeklyArtistChart
		user.recentWeeklyAlbumChart
		user.recentWeeklyTrackChart
		user.systemRecommendations
		user.currentEvents
		user.friendsEvents
		user.systemEventRecommendations
		
	The User class exposes the following instance methods:
	
		user.AsDict()
		user.FillProfileData()
		user.GetCommonArtists(withUser)
		user.GetCompatibility(withUser)
		user.GetCurrentEvents()
		user.GetFriends(fullProfile)
		user.GetFriendsEvents()
		user.GetNeighbours(fullProfile)
		user.GetRecentBannedTracks()
		user.GetRecentLovedTracks()
		user.GetRecentPlayedTracks()
		user.GetSystemEventRecommendations()
		user.GetSystemRecommendations()
		user.GetTasteOmeter(withUser)
		user.GetTopAlbums(kind)
		user.GetTopArtists(kind)
		user.GetTopTags(artist, album, track)
		user.GetTopTracks(kind)
		user.GetWeeklyAlbumChart(start, end)
		user.GetWeeklyAlbumChartList()
		user.GetWeeklyArtistChart(start, end)
		user.GetWeeklyArtistChartList()
		user.GetWeeklyTrackChart(start, end)
		user.GetWeeklyTrackChartList()
	"""
	#map of user attributes for extracting them from received data
	attributeMap = {
		'id': ("['@id']", int),
		'cluster': ("['@cluster']", int),
		'username': ("['@username']", str),
		'url': ("['url']", str),
		'realname': ("['realname']", str),
		'registered': (
			"['registered']['@unixtime']",
			lambda x: datetime.fromtimestamp(int(x))
		),
		'age': ("['age']", int),
		'gender': ("['gender']", str),
		'country': ("['country']",
			lambda x: Country(name = x)
			),
		'playcount': ("['playcount']", int),
		'avatar': ("['avatar']", str),
		'icon': ("['icon']", str)
	}

	def __init__(self,
				 id = None,
				 cluster = None,
				 username = None,
				 url = None,
				 realname = None,
				 registered = None,
				 age = None,
				 gender = None,
				 country = None,
				 playcount = None,
				 avatar = None,
				 icon = None):
		"""An object to hold an user.

		This class is normally instantiated by the Webservice class.

		Args:
			id: The unique id of this user
			cluster: The cluster of this user
			username: The username of this user
			url: The URL of this user's profile on last.fm
			realname: The real name of this user
			registered: The date on which this user registered
			age: The age of this user
			gender: The gender of this user
			country:
				The country of this user, returned as Country object
			playcount: The total playcount of this user
			avatar: The URL of the avatar of this user on last.fm
			icon: The URL of the icon of this user on last.fm

		Usgae:
			>>> import audioscrobblerws
			>>> ws = audioscrobblerws.Webservice()
			>>> user = ws.GetUser('abhin4v')
		"""
		self.id = id
		self.cluster = cluster
		self.username = username
		self.url = url
		self.realname = realname
		self.registered = registered
		self.age = age
		self.gender = gender
		self.country = Country(country)
		self.playcount = playcount
		self.avatar = avatar
		self.icon = icon

	fullProfile = property(lambda (self): self.registered != None,
		doc="boolean attribute indicating whether the user has all attributes filled")

	def FillProfileData(self):
		"""Fills the profile data of this user.

		Usage:
			>>> user = ws.GetUser('abhin4v')
			>>> friend = user.friends[0]
			>>> friend.fullProfile
			False
			>>> friend.FillProfileData()
			>>> friend.fullProfile
			True
		"""
		#get the user
		user = Webservice().GetUser(self.username)
		#copy the profile data from the user to self
		for k,v in User.attributeMap.items():
			self.__setattr__(k, user.__getattribute__(k))

	def GetTopArtists(self, kind = None):
		"""Get the top artists for this user.

		Args:
			kind:
				3, 6 or 12 for fetching rolling 3 months, 6 months or 12 months
				charts respectively. (default: None)

		Returns:
			An ordered list of Artist objects corresponding to this user's top artists.

		Usage:
			>>> user = ws.GetUser('abhin4v')
			>>> topartists = user.GetTopArtists()
			>>> [t.name for t in topartists[:10]]
			['Bon Jovi', 'Bryan Adams', 'U2', 'Aerosmith', 'Nelly Furtado',
			'The Pillows', 'Avril Lavigne', 'Dream Theater', 'Pink Floyd', 'Green Day']
		"""
		#raise an exception of username is not set
		if not self.username:
			raise AudioscrobblerError("username has to be set before calling this method")
		#the url to fetch
		url = "http://ws.audioscrobbler.com/1.0/user/%s/topartists.xml" % self.username

		if kind:
			if kind == 3 or kind == 6 or kind == 12:
				url = "%s?type=%smonth" % (url, kind)
			else:
				raise AudioscrobblerError("kind arg can be 3, 6 or 12 only")
		#fetch the xml data
		xml = Webservice()._FetchUrl(url)
		#convert the xml to python dict
		data = Xml2Dict(ElementTree.XML(xml))

		artists = []
		#if no artists in data then return empty list
		if not 'artist' in data:
			return artists

		for artistData in data['artist']:
			#create new artist
			artist = Artist(
				name = artistData['name'],
				url = artistData['url'],
				thumbnail = artistData['thumbnail'],
				image = artistData['image']
			)
			#fill extra attribues
			if 'mbid' in artistData:
				artist.mbid = artistData['mbid']
			artist.rank = int(artistData['rank'])
			artist.playcount = int(artistData['playcount'])
			artists.append(artist)

		#return the list
		return artists

	topArtists = property(GetTopArtists,
		doc = "Top artists for this user")
	topArtist = property(lambda (self): self.GetTopArtists()[0],
		doc = "Topmost artist for this user")

	def GetTopAlbums(self, kind = None):
		"""Get the top albums for this user.

		Args:
			kind:
				3, 6 or 12 for fetching rolling 3 months, 6 months or 12 months
				charts respectively. (default: None)

		Returns:
			An ordered list of Album objects corresponding to this user's top albums.

		Usage:
			>>> user = ws.GetUser('abhin4v')
			>>> topalbums = user.GetTopAlbums()
			>>> [t.name for t in topalbums[:10]]
			['Have a Nice Day', 'Folklore', 'On a Day Like Today', "All That You Can't Leave Behind",
			'American Idiot', 'Loose', 'Songs About Jane', 'The Best of Me', 'Crush', 'These Days']
		"""
		if not self.username:
			raise AudioscrobblerError("username has to be set before calling this method")
		url = "http://ws.audioscrobbler.com/1.0/user/%s/topalbums.xml" % self.username

		if kind:
			if kind == 3 or kind == 6 or kind == 12:
				url = "%s?type=%smonth" % (url, kind)
			else:
				raise AudioscrobblerError("kind arg can be 3, 6 or 12 only")
		xml = Webservice()._FetchUrl(url)
		data = Xml2Dict(ElementTree.XML(xml))
		albums = []
		if not 'album' in data:
			return albums

		for albumData in data['album']:
			album = Album(
				name = albumData['name'],
				url = albumData['url'],
				coverart = albumData['image'])
			albumArtist = Artist(name = albumData['artist']['text'])
			if '@mbid' in albumData['artist']:
				albumArtist.mbid = albumData['artist']['@mbid']
			album.artist = albumArtist
			if 'mbid' in albumData:
				album.mbid = albumData['mbid']
			album.playcount = int(albumData['playcount'])
			album.rank = int(albumData['rank'])
			albums.append(album)
		return albums

	topAlbums = property(GetTopAlbums,
		doc = "Top albums for this user")
	topAlbum = property(lambda (self): self.GetTopAlbums()[0],
		doc = "Topmost album for this user")

	def GetTopTracks(self, kind = None):
		"""Get the top tracks for this user.

		Args:
			kind:
				3, 6 or 12 for fetching rolling 3 months, 6 months or 12 months
				charts respectively. (default: None)

		Returns:
			An ordered list of Tracks objects corresponding to this user's
			top tracks.

		Usage:
			>>> user = ws.GetUser('abhin4v')
			>>> toptracks = user.GetTopTracks()
			>>> [t.name for t in toptracks[:10]]
			['Toss the Feathers', 'I Remember You', 'Always', 'The Reason', "Cryin'",
			'Ride on Shooting Star', "I Don't Want to Miss a Thing", 'Heaven',
			"Things I'll Never Say", "Rever's Edge (B-6)"]
		"""
		if not self.username:
			raise AudioscrobblerError("username has to be set before calling this method")
		url = "http://ws.audioscrobbler.com/1.0/user/%s/toptracks.xml" % self.username

		if kind:
			if kind == 3 or kind == 6 or kind == 12:
				url = "%s?type=%smonth" % (url, kind)
			else:
				raise AudioscrobblerError("kind arg can be 3, 6 or 12 only")
		xml = Webservice()._FetchUrl(url)
		data = Xml2Dict(ElementTree.XML(xml))
		tracks = []
		if not 'track' in data:
			return tracks

		for trackData in data['track']:
			track = Track(
				name = trackData['name'],
				url = trackData['url']
			)
			trackArtist = Artist(name = trackData['artist']['text'])
			if '@mbid' in trackData['artist']:
				trackArtist.mbid = trackData['artist']['@mbid']
			track.artist = trackArtist
			if 'mbid' in trackData:
				track.mbid = trackData['mbid']
			track.playcount = int(trackData['playcount'])
			track.rank = int(trackData['rank'])
			tracks.append(track)
		return tracks

	topTracks = property(GetTopTracks,
		doc = "Top tracks for this user")
	topTrack = property(lambda (self): self.GetTopTracks()[0],
		doc = "Topmost track for this user")

	def GetTopTags(self,
				   artist = None,
				   album = None,
				   track = None):
		"""Get the top tags for this user.

		Args:
			artist: name of the artist for which top tags are to be fetched (default: None)
			album: name of the album for which top tags are to be fetched (default: None)
			track: name of the track for which top tags are to be fetched (default: None)

		If album or track argument is provided then the artist argument must be
		provided too.

		Returns:
			An ordered list of Tag objects corresponding to this user's top tags
			(corresponding to artist and album/track if provided).

		Usage:
			>>> user = ws.GetUser('abhin4v')
			>>> toptags = user.GetTopTags()
			>>> [t.name for t in toptags[:10]]
			['rock', 'classic rock', 'anime', 'anime music', 'anime ost', 'jpop',
			'ost', 'japanese', 'loved', 'soundtrack']

		"""
		if not self.username:
			raise AudioscrobblerError("username has to be set before calling this method")
		#choose the url based on whether its for artist, album or track or none
		if artist:
			if album:
				url = "http://ws.audioscrobbler.com/1.0/user/%s/albumtags.xml" % self.username
				parameters = {
					'artist': artist,
					'album': album
				}
			elif track:
				url = "http://ws.audioscrobbler.com/1.0/user/%s/tracktags.xml" % self.username
				parameters = {
					'artist': artist,
					'track': track
				}
			else:
				url = "http://ws.audioscrobbler.com/1.0/user/%s/artisttags.xml" % self.username
				parameters = {
					'artist': artist
				}
		else:
			url = "http://ws.audioscrobbler.com/1.0/user/%s/tags.xml" % self.username
			parameters = {}

		xml = Webservice()._FetchUrl(url, parameters)
		data = Xml2Dict(ElementTree.XML(xml))
		tags = []
		if not 'tag' in data:
			return tags

		for tagData in data['tag']:
			tag = Tag(
				name = tagData['name'],
				url = tagData['url']
			)
			tag.count = int(tagData['count'])
			tags.append(tag)
		return tags

	topTags = property(GetTopTags,
		doc = "Top tags for this user")
	topTag = property(lambda (self): self.GetTopTags()[0],
		doc = "Topmost tag for this user")

	def GetRelatedUser(self, relation, fullProfile = False):
		"""Get the related users (friends and neighbours) of this user.

		This method is not meant to be used directly. Use user.GetFriends() and
		user.GetNeighbours() instead.

		Args:
			relation: relation with this user. can be 'friends' or 'neighbours'
			fullProfile:
				if True, full profile data of all related users is fetched. can
				be slow if the number of related users is large. (default: False)

		Returns:
			A ordered list of User objects corresponding to this user's related user.
		"""
		if not self.username:
			raise AudioscrobblerError("username has to be set before calling this method")

		if relation != 'friends' and relation != 'neighbours':
			raise AudioscrobblerError("relation can either be 'friends' or 'neighbours'")

		url = "http://ws.audioscrobbler.com/1.0/user/%s/%s.xml" %\
				(self.username, relation)
		xml = Webservice()._FetchUrl(url)
		data = Xml2Dict(ElementTree.XML(xml))

		if fullProfile:
			relatedUsers = [Webservice().GetUser(userData['@username'])
						for userData in data['user'] if 'user' in data]
		else:
			relatedUsers = []
			if not 'user' in data:
				return relatedUsers

			for userData in data['user']:
				relatedUser = User(
								username = userData['@username'],
								url = userData['url'],
								avatar = userData['image']
							)
				if relation == "neighbours":
					relatedUser.match = userData['match']
				relatedUsers.append(relatedUser)

		return relatedUsers

	def GetFriends(self, fullProfile = False):
		"""Get the friends of this user.

		Args:
			fullProfile:
				if True, full profile data of all friends is fetched. can
				be slow if the number of friends is large. (default: False)

		Returns:
			A list of User objects corresponding to this user's friends.

		Usage:
			>>> user = ws.GetUser('abhin4v')
			>>> friends = user.GetFriends()
			>>> [f.username for f in friends[:10]]
			['aalaap', 'arjunghosh', 'manatoonie', 'prototypeangel', 'sathyabhat',
			'phonegger', 'n9986', 'Brajeshwar', 'holden_X', 'nikunj_nikki_ya']
		"""
		return self.GetRelatedUser("friends", fullProfile)

	friends = property(GetFriends,
		doc = "Friends of this user")

	def GetNeighbours(self, fullProfile = False):
		"""Get the neighbours of this user.

		Args:
			fullProfile:
				if True, full profile data of all neighbours is fetched. can
				be slow if the number of neighbours is large. (default: False)

		Returns:
			A ordered list of User objects corresponding to this user's neighbours.

		Usage:
			>>> user = ws.GetUser('abhin4v')
			>>> neighbours = user.GetNeighbours()
			>>> [n.username for n in neighbours[:10]]
			['Berestel', 'Seriall', 'Nikos9', 'alice-e', 'DB911', 'smost123',
			'mattj85', 'Graeme369', 'draqon', 'Huaba']
		"""
		return self.GetRelatedUser("neighbours", fullProfile)

	neighbours = property(GetNeighbours,
		doc = "Neighbours of this user")

	def GetRecentTracks(self, kind):
		"""Get the recent tracks for this user.

		This method is not meant to be used directly. Use user.GetRecentPlayedTracks(),
		user.GetRecentBannedTracks() and user.GetRecentLovedTracks() instead.

		Args:
			kind: type of recent tracks. can be "played", "banned" or "loved"

		Returns:
			An ordered list of Track objects corresponding to this user's recent tracks and
			the 'kind' provided.
		"""
		if not self.username:
			raise AudioscrobblerError("username has to be set before calling this method")

		kindMap = {
			"played": "",
			"banned": "banned",
			"loved": "loved"
		}
		url = "http://ws.audioscrobbler.com/1.0/user/%s/recent%stracks.xml" %\
				(self.username, kindMap[kind])

		xml = Webservice()._FetchUrl(url, no_cache = True)
		data = Xml2Dict(ElementTree.XML(xml))
		tracks = []
		if not 'track' in data:
			return tracks

		for trackData in data['track']:
			track = Track(
				name = trackData['name'],
				url = trackData['url']
			)

			trackArtist = Artist(name = trackData['artist']['text'])
			if '@mbid' in trackData['artist']:
				trackArtist.mbid = trackData['artist']['@mbid']
			track.artist = trackArtist

			if kind == "played":
				if 'text' in trackData['album']:
					trackAlbum = Album(name = trackData['album']['text'])
					if '@mbid' in trackData['album']:
						trackArtist.mbid = trackData['artist']['@mbid']
					track.album = trackAlbum
				track.streamable = trackData['@streamable'] == 'true'


			if 'mbid' in trackData:
				track.mbid = trackData['mbid']

			track.date = datetime.fromtimestamp(int(trackData['date']['@uts']))
			tracks.append(track)
		return tracks

	def GetRecentPlayedTracks(self):
		"""Get the recently played tracks for this user.

		Returns:
			An ordered list of Track objects corresponding to this user's recently played tracks.

		Usage:
			>>> user = ws.GetUser('abhin4v')
			>>> rpt = user.GetRecentPlayedTracks()
			>>> [t.name for t in rpt]
			['In My Place', 'Fading Like a Flower (Every Time You Leave)', 'High Hopes',
			'Do It', 'Centerfold', 'Sleep', 'Duur', 'I Want to Be Loved',
			'Destination Anywhere', 'Tangled Up in Me']
		"""
		return self.GetRecentTracks("played")

	recentPlayedTracks = property(GetRecentPlayedTracks,
		doc = "Recently played tracks for this user")
	mostRecentPlayedTrack = property(lambda (self): len(self.GetRecentPlayedTracks()) and self.GetRecentPlayedTracks()[0] or None,
		doc = "Most recently played track for this user")

	def GetRecentBannedTracks(self):
		"""Get the recently banned tracks for this user.

		Returns:
			An ordered list of Track objects corresponding to this user's recently banned tracks.

		Usage:
			>>> user = ws.GetUser('abhin4v')
			>>> rbt = user.GetRecentBannedTracks()
			>>> [t.name for t in rbt]
			['Snakes', 'Talk']
		"""
		return self.GetRecentTracks("banned")

	recentBannedTracks = property(GetRecentBannedTracks,
		doc = "Recently banned tracks for this user")

	def GetRecentLovedTracks(self):
		"""Get the recently loved tracks for this user.

		Returns:
			An ordered list of Track objects corresponding to this user's recently loved tracks.

		Usage:
			>>> user = ws.GetUser('abhin4v')
			>>> rlt = user.GetRecentLovedTracks()
			>>> [t.name for t in rlt]
			['Dr. Stein', 'The Final Countdown', 'Is This Love', 'Innuendo',
			'Battlestar Scralatchtica', 'Self Destruction, Final', 'Letter Read',
			'Hey There Delilah', 'Gideon', 'Kiss the Dawn']
		"""
		return self.GetRecentTracks("loved")

	recentLovedTracks = property(GetRecentLovedTracks,
		doc = "Recently loved tracks for this user")

	def GetWeeklyChart(self, kind, start = None, end = None):
		"""Get weekly chart for this user.

		This method is not meant to be called directly. Use user.GetWeeklyArtistChart(),
		user.GetWeeklyAlbumChart() and user.GetWeeklyTrackChart() instead.

		Args:
			kind: type of the weekly chart to fetch. can be 'artist', 'album' or 'track'.
			start:
				time for the start of the chart. can be datetime.datetime object
				or a unix timestamp. (default: None)
			end:
				time for the end of the chart. can be datetime.datetime object
				or a unix timestamp. (default: None)

		Returns:
			A WeeklyArtistChart, WeeklyAlbumChart or WeeklyTrackChart object depending
			upon the 'kind' provided.
		"""
		if not self.username:
			raise AudioscrobblerError("username has to be set before calling this method")
		return eval("Weekly" + kind.title() + "Chart")(
			user = self.username,
			start = start,
			end = end
		)

	def GetWeeklyArtistChart(self, start = None, end = None):
		"""Get weekly artist chart for this user.

		Args:
			start:
				time for the start of the chart. can be datetime.datetime object
				or a unix timestamp. (default: None)
			end:
				time for the end of the chart. can be datetime.datetime object
				or a unix timestamp. (default: None)

		Returns:
			A WeeklyArtistChart object. If 'start' and 'end' are not provided then
			the chart for the most recent week is returned. Else the chart for the
			specified week is returned.

		Usage:
			>>> user = ws.GetUser('abhin4v')
			>>> wac = user.GetWeeklyArtistChart()
			>>> wac.start
			datetime.datetime(2008, 5, 11, 17, 30)
			>>> wac.end
			datetime.datetime(2008, 5, 18, 17, 30)
			>>> [(a.name, a.playcount) for a in wac.artists[:10]]
			[('Bon Jovi', 130), ('Roxette', 52), ('Poets of the Fall', 37),
			('Aerosmith', 23), ('Def Leppard', 15), ('U2', 13), ('Dream Theater', 13),
			('Bryan Adams', 13), ('Jon Bon Jovi', 13), ('The Corrs', 11)]
		"""
		return self.GetWeeklyChart("artist", start, end)

	recentWeeklyArtistChart = property(GetWeeklyArtistChart,
		doc = "Weekly artist chart of the most recent week for this user")

	def GetWeeklyAlbumChart(self, start = None, end = None):
		"""Get weekly album chart for this user.

		Args:
			start:
				time for the start of the chart. can be datetime.datetime object
				or a unix timestamp. (default: None)
			end:
				time for the end of the chart. can be datetime.datetime object
				or a unix timestamp. (default: None)

		Returns:
			A WeeklyAlbumChart object. If 'start' and 'end' are not provided then
			the chart for the most recent week is returned. Else the chart for the
			specified week is returned.

		Usage:
			>>> user = ws.GetUser('abhin4v')
			>>> wac = user.GetWeeklyAlbumChart()
			>>> wac.start
			datetime.datetime(2008, 5, 11, 17, 30)
			>>> wac.end
			datetime.datetime(2008, 5, 18, 17, 30)
			>>> [(a.name, a.playcount) for a in wac.albums[:10]]
			[('Signs of Life', 17), ('Have a Nice Day', 16), ('These Days', 15),
			('Crossroad: The Best of Bon Jovi', 15), ('Bounce', 13), ('Keep the Faith', 11),
			('Destination Anywhere', 10), ('Lost Highway', 10), ('Crush', 10),
			('This Left Feels Right: Greatest Hits With a Twist', 9)]
		"""
		return self.GetWeeklyChart("album", start, end)

	recentWeeklyAlbumChart = property(GetWeeklyAlbumChart,
		doc = "Weekly album chart of the most recent week for this user")

	def GetWeeklyTrackChart(self, start = None, end = None):
		"""Get weekly track chart for this user.

		Args:
			start:
				time for the start of the chart. can be datetime.datetime object
				or a unix timestamp. (default: None)
			end:
				time for the end of the chart. can be datetime.datetime object
				or a unix timestamp. (default: None)

		Returns:
			A WeeklyTrackChart object. If 'start' and 'end' are not provided then
			the chart for the most recent week is returned. Else the chart for the
			specified week is returned.

		Usage:
			>>> user = ws.GetUser('abhin4v')
			>>> wtc = user.GetWeeklyTrackChart()
			>>> wtc.start
			datetime.datetime(2008, 5, 11, 17, 30)
			>>> wtc.end
			datetime.datetime(2008, 5, 18, 17, 30)
			>>> [(a.name, a.playcount) for a in wtc.tracks[:5]]
			[('June Afternoon', 10), ('Sleeping in My Car (single edit)', 8),
			('Everyday', 6), ("She Doesn't Live Here Anymore", 5), ('Wanted Dead or Alive', 5)]
		"""
		return self.GetWeeklyChart("track", start, end)

	recentWeeklyTrackChart = property(GetWeeklyTrackChart,
		doc = "Weekly track chart of the most recent week for this user")

	def GetWeeklyChartList(self, kind = None):
		"""Get list of weekly charts of all weeks for this user.

		Args:
			kind:
				type of chart to fetch. can be 'artist', 'album' or 'track'.
				(default: None)

		Returns:
			An ordered list of WeeklyArtistChart, WeeklyAlbumChart or WeeklyTrackChart objects
			depending upon the 'kind' provided. If 'kind' is not provided then a
			list of WeeklyChart is returned.

		Usage:
			>>> user = ws.GetUser('abhin4v')
			>>> wcl = user.GetWeeklyChartList()
			>>> [(wc.start, wc.end) for wc in wcl[:3]]
			[(datetime.datetime(2006, 2, 12, 17, 30, 1), datetime.datetime(2006, 2, 19, 17, 30, 1)),
			(datetime.datetime(2006, 2, 19, 17, 30, 1), datetime.datetime(2006, 2, 26, 17, 30, 1)),
			(datetime.datetime(2006, 2, 26, 17, 30, 3), datetime.datetime(2006, 3, 5, 17, 30, 3))]
		"""
		if not self.username:
			raise AudioscrobblerError("username has to be set before calling this method")

		url = "http://ws.audioscrobbler.com/1.0/user/%s/weeklychartlist.xml" % self.username
		xml = Webservice()._FetchUrl(url)
		data = Xml2Dict(ElementTree.XML(xml))

		weeklychartlist = []
		if not 'chart' in data:
			return weeklychartlist

		if kind != None:
			for chartData in data['chart']:
				try:
					weeklychartlist.append(
						eval("Weekly" + kind.title() + "Chart")(
							user = self.username,
							start = int(chartData['@from']),
							end = int(chartData['@to'])
						)
					)
				except SyntaxError:
					pass
		else:
			for chartData in data['chart']:
				weeklychartlist.append(
					WeeklyChart(
						user = self.username,
						start = int(chartData['@from']),
						end = int(chartData['@to'])
					)
				)

		return weeklychartlist

	def GetWeeklyArtistChartList(self):
		"""Get a list of all weekly artist charts of all weeks for this user.

		Returns:
			An ordered list of WeeklyArtistChart objects.
		"""
		return self.GetWeeklyChartList("artist")

	def GetWeeklyAlbumChartList(self):
		"""Get a list of all weekly album charts of all weeks for this user.

		Returns:
			An ordered list of WeeklyAlbumChart objects.
		"""
		return self.GetWeeklyChartList("album")

	def GetWeeklyTrackChartList(self):
		"""Get a list of all weekly track charts of all weeks for this user.

		Returns:
			An ordered list of WeeklyTrackChart objects.
		"""
		return self.GetWeeklyChartList("track")

	def GetSystemRecommendations(self):
		"""Get a list of artists recommended to this user by the system.

		Returns:
			A list of Artist objects which have been recommended to this user by the system.

		Usage:
			>>> user = ws.GetUser('abhin4v')
			>>> recs = user.GetSystemRecommendations()
			>>> [r.name for r in recs[:10]]
			['Bryan Adams, Rod Stewart & Sting', 'Richie Sambora', 'Queen + Paul Rodgers',
			'Andrea Corr', 'Sunrise Avenue', 'Brian McFadden', 'Roger Taylor',
			'Chris Rea', 'David Knopfler', 'Gary Barlow']
		"""
		if not self.username:
			raise AudioscrobblerError("username has to be set before calling this method")
		url = "http://ws.audioscrobbler.com/1.0/user/%s/systemrecs.xml" % self.username
		xml = Webservice()._FetchUrl(url)
		data = Xml2Dict(ElementTree.XML(xml))

		artists = []
		if not 'artist' in data:
			return artists

		for artistData in data['artist']:
			artist = Artist(
				name = artistData['name'],
				url = artistData['url'],
			)
			if 'mbid' in artistData:
				artist.mbid = artistData['mbid']
			artists.append(artist)
		return artists

	systemRecommendations = property(GetSystemRecommendations,
		doc = "Artists recommended by the system to this user")

	def GetTasteOmeter(self, withUser):
		"""Get the taste-o-meter for this user with another user.

		Args:
			withUser: user to find the taste-o-meter of this user with. can be a
			string (username) or an User object.

		Returns:
			A TasteOmeter object with following attributes:
				score: compatibility score between this user and the provided user.
				commonArtists:
					a list of Artist objects which are common to
					this user and the provided user.

		Usage:
			>>> user = ws.GetUser('abhin4v')
			>>> taste = user.GetTasteOmeter(user.friends[0])
			>>> taste.score
			0.064650100000000002
			>>> [a.name for a in taste.commonArtists[:10]]
			['Linkin Park', 'U2', 'Nelly Furtado', 'Foo Fighters', 'Enigma',
			'Avril Lavigne', 'Sting', 'Moby', 'Robbie Williams', 'Paul van Dyk']
		"""
		if not self.username:
			raise AudioscrobblerError("username has to be set before calling this method")
		url = "http://ws.audioscrobbler.com/1.0/user/%s/tasteometer.xml" % self.username
		if type(withUser) == User:
			parameters = {'with': withUser.username}
		elif type(withUser) == types.StringType:
			parameters = {'with': withUser}
		else:
			raise AudioscrobblerError("a second user must be specied as argument")
		xml = Webservice()._FetchUrl(url, parameters)
		data = Xml2Dict(ElementTree.XML(xml))

		class TasteOmeter (object):
			def __init__(self, score, commonartists):
				self.score = score
				self.commonArtists = commonartists

		artists = [Artist(name = artist['name']) for artist in data['commonArtists']['artist']]

		return TasteOmeter(float(data['score']), artists)

	def GetCompatibility(self, withUser):
		"""Get compatibility score between this user and another user.

		Args:
			withUser: user to find the compatibility score of this user with.
			can be a string (username) or an User object.

		Returns:
			The compatibility score between this user and the provided user, as a float.

		Usage:
			>>> user = ws.GetUser('abhin4v')
			>>> [(f.username, user.GetCompatibility(f)) for f in user.friends[:5]]
			[('aalaap', 0.064650100000000002), ('arjunghosh', 0.37828899999999999),
			('manatoonie', 0.270144), ('prototypeangel', 0.12955900000000001),
			('sathyabhat', 0.55462199999999995)]
		"""
		return self.GetTasteOmeter(withUser).score

	def GetCommonArtists(self, withUser):
		"""Get the common artists between this user and another user.

		Args:
			withUser: user to find the common artists of this user with.
			can be a string (username) or an User object.

		Returns:
			A list of Artist objects which are common to this user and the provided user.

		Usage:
			>>> user = ws.GetUser('abhin4v')
			>>> ca = user.GetCommonArtists(user.neighbours[0])
			>>> [a.name for a in ca[:10]]
			['Bon Jovi', 'Bryan Adams', 'Aerosmith', 'U2', 'Nickelback', 'Def Leppard',
			'Skid Row', '3 Doors Down', 'Avril Lavigne', 'Linkin Park']
		"""
		return self.GetTasteOmeter(withUser).commonArtists

	def GetCurrentEvents(self):
		"""Get the current events that this user is attending.

		Returns:
			A list of Event objects which this user is attending currently.

		Usage:
			>>> user = ws.GetUser('schlagschnitzel')
			>>> events = user.GetCurrentEvents()
			>>> for event in events:
			...     print event.venue
			...
			229 great portland street, 229 Great Portland Street, London, W1W 5PN, United Kingdom
			The Legion, 348 Old Street, London, EC1V 9NQ, United Kingdom
			Rough Trade East, 'Dray Walk', Old Truman Brewery, 91 Brick Lane, London, E1 6QL, United Kingdom
			Dingwalls, Camden, London, NW1 8AB, United Kingdom
			The Fly, 36-38 New Oxford St, London, WC1A 1EP, United Kingdom
			Shepherds Bush Empire, London, United Kingdom
			The Metro Club, 19-23 Oxford Street, London, W1D 2DN, United Kingdom
			Hill Farm, Steventon, United Kingdom
			Clapham Common, London, United Kingdom
			Dingwalls, Camden, London, NW1 8AB, United Kingdom
			Victoria Park, London, E9, United Kingdom
			Koko, 1a Camden High Street, Camden, London, NW1 7JE, United Kingdom
			Astoria, 157 Charing Cross Road, London, WC2H 0EL, United Kingdom
		"""
		if not self.username:
			raise AudioscrobblerError("username has to be set before calling this method")
		url = "http://ws.audioscrobbler.com/1.0/user/%s/events.ics" % self.username

		return Event.fromICal(url)

	currentEvents = property(GetCurrentEvents,
		doc = "Current events which this user is attending")

	def GetFriendsEvents(self):
		"""Get the events which this user's friends are attending.

		Returns:
			A list of Event objects which this user's friends are attending.

		Usage:
			>>> user = ws.GetUser('schlagschnitzel')
			>>> events = user.GetFriendsEvents()
			>>> for event in events[0:5]:
			...     print event.venue
			...
			La Cigale, 120 bd Rochechouart, 75018 Paris, France
			Paradiso, Weteringschans 6-8, 1017 SG Amsterdam, Netherlands
			Barden's Boudoir, 38-44 Stoke Newington Road, London, N16 7XJ, United Kingdom
			Logo, Grindelallee 5, Hamburg, Germany
			Beaconsfield Gallery, 22 Newport Street, London, SE11 6AY, United Kingdom
		"""
		if not self.username:
			raise AudioscrobblerError("username has to be set before calling this method")
		url = "http://ws.audioscrobbler.com/1.0/user/%s/friendevents.ics" % self.username

		return Event.fromICal(url)

	friendsEvents = property(GetFriendsEvents,
		doc = "Events whcih this user's friends are attending")

	def GetSystemEventRecommendations(self):
		"""Get the events recommended to this user by the system.

		Returns:
			A list of Event objects which are recommended to this user by the system.

		Usage:
			>>> user = ws.GetUser('RJ')
			>>> events = user.GetSystemEventRecommendations()
			>>> for event in events[0:5]:
			...     print event.venue
			...
			Wembley Stadium, Empire Way, London, HA9 0WS, United Kingdom
			Theatre Royal, Haymarket, Haymarket, London, SW1Y 4HT, United Kingdom
			Apollo Hammersmith, Queen Caroline St, Hammersmith, W6 9QH, United Kingdom
			93 Feet East, 150 Brick Lane, London, E1 6QL, United Kingdom
			The Macbeth, 70 Hoxton Street, London, N1 6LP, United Kingdom
		"""
		if not self.username:
			raise AudioscrobblerError("username has to be set before calling this method")
		url = "http://ws.audioscrobbler.com/1.0/user/%s/eventsysrecs.ics" % self.username

		return Event.fromICal(url)

	systemEventRecommendations = property(GetSystemEventRecommendations,
		doc = "Events recommended to this user by the system")

	def AsDict(self):
		"""Return self as a dict."""
		return self.__dict__

	def __eq__(self, other):
		return self.username == other.username

	def __ne__(self, other):
		return not self.__eq__(other)

import cElementTree as ElementTree
import types
from datetime import datetime

from album import Album
from artist import Artist
from webservice import Webservice
from country import Country
from event import Event
from tag import Tag
from track import Track
from weeklychart import WeeklyChart, WeeklyArtistChart, WeeklyAlbumChart, WeeklyTrackChart
from xml2dict import Xml2Dict
from error import AudioscrobblerError
